package com.longe.config;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.longe.security.*;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Properties;

@Configuration
public class shiroConfig {

    /**
     * shiro过滤器
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        /**创建过滤工厂**/
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        /**设置安全管理器**/
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        /**loginUrl认证提交地址，如果没有认证将会请求此地址进行认证,对于前后端分离的项目,
         value可以指定一个接口返回特殊的状态码,前端获取到该状态码后知道是没有认证的,然后由前端来跳转至登陆页面**/
        //这里设置/,因为http://localhost:8080/,默认会去寻找index.html文件,相当于http://localhost:8080/index.html
        shiroFilterFactoryBean.setLoginUrl("/");
        /**已经认证,但是用户没有权限的时候跳转地址**/
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
        /**设置过滤器**/
        //把自定义的过滤器添加到过滤器集合中
        Map<String, Filter> myFilterMap = new HashMap<>();
        myFilterMap.put("jwt", new JwtFilter());
        myFilterMap.put("myPermission", new MyPermissionFilter());
        shiroFilterFactoryBean.setFilters(myFilterMap);
        // 自定义url规则使用LinkedHashMap有序Map
        LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>(16);
        filterChainDefinitionMap.put("/*.ico", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        filterChainDefinitionMap.put("/test", "anon");
        filterChainDefinitionMap.put("/error/token", "anon");

        //filterChainDefinitionMap.put("/customer/update", "perms[customer:update]");这种方式在no session情况下好像不能使用
        filterChainDefinitionMap.put("/customize/**", "jwt,myPermission");

        filterChainDefinitionMap.put("/**", "jwt");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 解决使用shiro注解的资源没有权限时抛出异常而不跳转到指定页面
     * 如果这个也解决不了，那就自定义一个异常进行捕捉处理吧
     * @return
     */
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();

        /**未授权处理页**/
        properties.setProperty("org.apache.shiro.authz.UnauthorizedException", "/unauthorized");
        /**身份没有验证**/
        properties.setProperty("org.apache.shiro.authz.UnauthenticatedException", "/");
        resolver.setExceptionMappings(properties);
        return resolver;
    }

    /**
     * 安全管理器
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm());

        /** 关闭shiro自带的session **/
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        securityManager.setSubjectFactory(new StatelessDefaultSubjectFactory());

        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        securityManager.setSessionManager(sessionManager);

        securityManager.setCacheManager(redisCacheManager());

        return securityManager;
    }

    /**
     * 自定义ream
     * @return
     */
    @Bean
    public MyRealm myRealm() {
        MyRealm myRealm = new MyRealm();
        /**设置凭证匹配器**/
        //myRealm.setCredentialsMatcher(credentialsMatcher());

        /**使用缓存,这里注释掉认证相关**/
        myRealm.setCachingEnabled(true);
        //启用授权缓存，即缓存AuthorizationInfo信息，默认false
        myRealm.setAuthorizationCachingEnabled(true);
        //缓存AuthorizationInfo信息的缓存名称 对应redis中相应的key
        myRealm.setAuthorizationCacheName("authorizationCache");

        /**myReam中注入自定义的PermissionResolver**/
        myRealm.setPermissionResolver(new MyPermissionResolver());

        return myRealm;
    }

    /**
     * 凭证匹配器
     * @return
     */
    @Bean
    public HashedCredentialsMatcher credentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        /**md5加密**/
        credentialsMatcher.setHashAlgorithmName("md5");
        /**加密次数**/
        credentialsMatcher.setHashIterations(1);
        return credentialsMatcher;
    }

    /**
     * 配置shiro注解支持
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager());
        return advisor;
    }

    /**
     * 用于thymeleaf模板使用shiro标签
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }

    /**
     * redis的控制器，操作redis
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost("127.0.0.1:6379");
        return redisManager;
    }

    /**
     * 缓存管理器
     * @return
     */
    public RedisCacheManager redisCacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());

        /**以Principal中的userName属性作为redis中key**/
        redisCacheManager.setPrincipalIdFieldName("userName");

        return redisCacheManager;
    }
}